#include "Renderer2D.h"
#include "hzpch.h"

#include "RendererCommand.h"
#include "Shader.h"
#include "VertexArray.h"

#include <glm/gtc/matrix_transform.hpp>

namespace Hazel
{
struct QuadVertex
{
    glm::vec3 Position;
    glm::vec4 Color;
    glm::vec2 TexCoord;
    float TexIndex;
    float TilingFactor;
};
struct Renderer2DData
{
    static const uint32_t MaxQuads = 20000;
    static const uint32_t MaxVertices = MaxQuads * 4;
    static const uint32_t MaxIndices = MaxQuads * 6;
    static const uint32_t MaxTextureSlots = 32; // TODO: RanderCaps
    Ref<VertexArray> QuardVertexArray;
    Ref<VertexBuffer> QuardVertexBuffer;
    Ref<Shader> FlatColorShader;
    Ref<Shader> TextureShader;
    Ref<Texture2D> WhiteTexture;

    uint32_t QuadIndexCount = 0;
    QuadVertex *QuadVertexBufferBase = nullptr;
    QuadVertex *QuadVertexBufferPtr = nullptr;
    std::array<Ref<Texture2D>, MaxTextureSlots> TextureSlots;
    uint32_t TextureSlotIndex = 1; // 0 is white texture

    glm::vec4 QuadVertexPositions[4];

    Renderer2D::Statistcs Stats;
};

static Renderer2DData s_Data;

void Renderer2D::Init()
{
    HZ_PROFILE_FUNC();

    s_Data.QuardVertexArray = VertexArray::Create();

    s_Data.QuardVertexBuffer = VertexBuffer::Create(s_Data.MaxVertices * sizeof(QuadVertex));

    s_Data.QuardVertexBuffer->SetLayout({{ShaderDataType::Float3, "a_Position"},

                                         {ShaderDataType::Float4, "a_Color"},

                                         {ShaderDataType::Float2, "a_TexCoord"},

                                         {ShaderDataType::Float, "a_TexIndex"},

                                         {ShaderDataType::Float, "a_TilingFactor"}});
    s_Data.QuardVertexArray->AddVertexBuffer(s_Data.QuardVertexBuffer);

    s_Data.QuadVertexBufferBase = new QuadVertex[s_Data.MaxVertices];

    uint32_t *quadIndices = new uint32_t[s_Data.MaxIndices];
    uint32_t offset = 0;
    for (uint32_t i = 0; i < s_Data.MaxIndices; i += 6)
    {
        quadIndices[i + 0] = offset + 0;
        quadIndices[i + 1] = offset + 1;
        quadIndices[i + 2] = offset + 2;

        quadIndices[i + 3] = offset + 2;
        quadIndices[i + 4] = offset + 3;
        quadIndices[i + 5] = offset + 0;

        offset += 4;
    }

    Ref<IndexBuffer> squareIB = IndexBuffer::Create(quadIndices, s_Data.MaxIndices);
    s_Data.QuardVertexArray->SetIndexBuffer(squareIB);
    delete[] quadIndices;

    s_Data.WhiteTexture = Texture2D::Create(1, 1);
    uint32_t whiteTextureData = 0xffffffff;
    s_Data.WhiteTexture->SetData(&whiteTextureData, sizeof(uint32_t));

    int samplers[s_Data.MaxTextureSlots];
    for (int i = 0; i < s_Data.MaxTextureSlots; i++)
        samplers[i] = i;
    // s_Data.FlatColorShader = Shader::Create("../../assert/shaders/FlatColor.glsl");
    s_Data.TextureShader = Shader::Create("../../assert/shaders/Texture.glsl");
    s_Data.TextureShader->Bind();
    s_Data.TextureShader->SetIntArray("u_Textures", (int *)samplers, s_Data.MaxTextureSlots);

    // set all texture slot to 0
    // memset(s_Data.TextureSlots.data(), 0, s_Data.TextureSlots.size() * sizeof(uint32_t));
    s_Data.TextureSlots[0] = s_Data.WhiteTexture;

    s_Data.QuadVertexPositions[0] = {-0.5f, -0.5f, 0.0f, 1.0f};
    s_Data.QuadVertexPositions[1] = {0.5f, -0.5f, 0.0f, 1.0f};
    s_Data.QuadVertexPositions[2] = {0.5f, 0.5f, 0.0f, 1.0f};
    s_Data.QuadVertexPositions[3] = {-0.5f, 0.5f, 0.0f, 1.0f};
}
void Renderer2D::Shutdown()
{
    HZ_PROFILE_FUNC();

    delete[] s_Data.QuadVertexBufferBase;
}
void Renderer2D::BeginScene(const OrthographicCamera &camera)
{
    HZ_PROFILE_FUNC();

    // s_Data->FlatColorShader->Bind();
    // s_Data->FlatColorShader->SetMat4("u_ViewProjection", camera.GetViewProjectionMatrix());
    s_Data.QuadIndexCount = 0;
    s_Data.TextureShader->Bind();
    s_Data.TextureShader->SetMat4("u_ViewProjection", camera.GetViewProjectionMatrix());
    // s_Data->FlatColorShader->SetMat4("u_Transform", glm::mat4(1.0f));
    s_Data.QuadVertexBufferPtr = s_Data.QuadVertexBufferBase;
    s_Data.TextureSlotIndex = 1;
}
void Renderer2D::EndScene()
{
    HZ_PROFILE_FUNC();

    uint32_t dataSize = (uint32_t)((uint8_t *)s_Data.QuadVertexBufferPtr - (uint8_t *)s_Data.QuadVertexBufferBase);
    s_Data.QuardVertexBuffer->SetData(s_Data.QuadVertexBufferBase, dataSize);

    Flush();
}

void Renderer2D::Flush()
{
    if (s_Data.QuadIndexCount == 0)
        return;
    if (s_Data.QuadIndexCount > 0)
    {
        for (uint32_t i = 0; i < s_Data.TextureSlotIndex; i++)
            s_Data.TextureSlots[i]->Bind(i); // bind need set slot number if unset it was 0

        RendererCommand::DrawIndexed(s_Data.QuardVertexArray, s_Data.QuadIndexCount);
    }
    s_Data.Stats.DrawCalls++;
}

void Renderer2D::DrawQuad(const glm::vec2 &position, const glm::vec2 &size, const glm::vec4 &color)
{
    DrawQuad({position.x, position.y, 0.0f}, size, color);
}
void Renderer2D::DrawQuad(const glm::vec3 &position, const glm::vec2 &size, const glm::vec4 &color)
{
    HZ_PROFILE_FUNC();

    if (s_Data.QuadIndexCount >= s_Data.MaxIndices)
        FlushAndReset();

    const float textureIndex = 0.0f, tilingFactor = 1.0f;
    const glm::vec2 TexCoods[4] = {{0.0f, 0.0f}, {1.0f, 0.0f}, {1.0f, 1.0f}, {0.0f, 1.0f}};
    glm::mat4 transform =
        glm::translate(glm::mat4(1.0f), position) * glm::scale(glm::mat4(1.0f), {size.x, size.y, 1.0f});

    for (int i = 0; i < 4; i++)
    {
        s_Data.QuadVertexBufferPtr->Position = transform * s_Data.QuadVertexPositions[i];
        s_Data.QuadVertexBufferPtr->Color = color;
        s_Data.QuadVertexBufferPtr->TexCoord = TexCoods[i];
        s_Data.QuadVertexBufferPtr->TexIndex = textureIndex;
        s_Data.QuadVertexBufferPtr->TilingFactor = tilingFactor;
        s_Data.QuadVertexBufferPtr++;
    }

    s_Data.QuadIndexCount += 6;

    s_Data.Stats.QuadCount++;
}

void Renderer2D::DrawQuad(const glm::vec2 &position, const glm::vec2 &size, const Ref<Texture2D> &texture,
                          float tilingfactor, const glm::vec4 &tilingcolor)
{
    DrawQuad({position.x, position.y, 0.0f}, size, texture, tilingfactor, tilingcolor);
}
void Renderer2D::DrawQuad(const glm::vec3 &position, const glm::vec2 &size, const Ref<Texture2D> &texture,
                          float tilingfactor, const glm::vec4 &tilingcolor)
{
    HZ_PROFILE_FUNC();

    // float x = 2, y = 2;
    // constexpr float sheetWidth = 272.0f, sheetHeight = 128.0f;
    // constexpr float spriteWidth = 16.0f, spriteHeight = 16.0f;

    if (s_Data.QuadIndexCount >= s_Data.MaxIndices)
        FlushAndReset();

    float textureIndex = 0.0f;
    for (uint32_t i = 1; i < s_Data.TextureSlotIndex; i++)
    {
        if (*texture.get() == *s_Data.TextureSlots.at(i).get())
        {
            textureIndex = (float)i;
            break;
        }
    }

    if (textureIndex == 0.0f)
    {
        textureIndex = (float)s_Data.TextureSlotIndex;
        s_Data.TextureSlots[(int)textureIndex] = texture;
        s_Data.TextureSlotIndex++;
    }

    glm::mat4 transform =
        glm::translate(glm::mat4(1.0f), position) * glm::scale(glm::mat4(1.0f), {size.x, size.y, 1.0f});

    constexpr glm::vec2 TexCoods[4] = {{0.0f, 0.0f}, {1.0f, 0.0f}, {1.0f, 1.0f}, {0.0f, 1.0f}};

    for (int i = 0; i < 4; i++)
    {
        s_Data.QuadVertexBufferPtr->Position = transform * s_Data.QuadVertexPositions[i];
        s_Data.QuadVertexBufferPtr->Color = tilingcolor;
        s_Data.QuadVertexBufferPtr->TexCoord = TexCoods[i];
        s_Data.QuadVertexBufferPtr->TexIndex = textureIndex;
        s_Data.QuadVertexBufferPtr->TilingFactor = tilingfactor;
        s_Data.QuadVertexBufferPtr++;
    }

    s_Data.QuadIndexCount += 6;

    s_Data.Stats.QuadCount++;
}

void Renderer2D::DrawQuad(const glm::vec2 &position, const glm::vec2 &size, const Ref<SubTexture2D> &subtexture,
                          float tilingfactor, const glm::vec4 &tilingcolor)
{
    DrawQuad({position.x, position.y, 0.0f}, size, subtexture, tilingfactor, tilingcolor);
}
void Renderer2D::DrawQuad(const glm::vec3 &position, const glm::vec2 &size, const Ref<SubTexture2D> &subtexture,
                          float tilingfactor, const glm::vec4 &tilingcolor)
{
    HZ_PROFILE_FUNC();

    // float x = 2, y = 2;
    // constexpr float sheetWidth = 272.0f, sheetHeight = 128.0f;
    // constexpr float spriteWidth = 16.0f, spriteHeight = 16.0f;
    
    if (s_Data.QuadIndexCount >= s_Data.MaxIndices)
        FlushAndReset();

    float textureIndex = 0.0f;
    for (uint32_t i = 1; i < s_Data.TextureSlotIndex; i++)
    {
        if (*subtexture->GetTexture().get() == *s_Data.TextureSlots.at(i).get())
        {
            textureIndex = (float)i;
            break;
        }
    }

    if (textureIndex == 0.0f)
    {
        textureIndex = (float)s_Data.TextureSlotIndex;
        s_Data.TextureSlots[(int)textureIndex] = subtexture->GetTexture();
        s_Data.TextureSlotIndex++;
    }

    glm::mat4 transform =
        glm::translate(glm::mat4(1.0f), position) * glm::scale(glm::mat4(1.0f), {size.x, size.y, 1.0f});

    for (int i = 0; i < 4; i++)
    {
        s_Data.QuadVertexBufferPtr->Position = transform * s_Data.QuadVertexPositions[i];
        s_Data.QuadVertexBufferPtr->Color = tilingcolor;
        s_Data.QuadVertexBufferPtr->TexCoord = subtexture->GetTexCoord()[i];
        s_Data.QuadVertexBufferPtr->TexIndex = textureIndex;
        s_Data.QuadVertexBufferPtr->TilingFactor = tilingfactor;
        s_Data.QuadVertexBufferPtr++;
    }

    s_Data.QuadIndexCount += 6;

    s_Data.Stats.QuadCount++;
}

void Renderer2D::DrawRotatedQuad(const glm::vec2 &position, const glm::vec2 &size, float rotation,
                                 const glm::vec4 &color)
{
    DrawRotatedQuad({position.x, position.y, 0.0f}, size, rotation, color);
}
void Renderer2D::DrawRotatedQuad(const glm::vec3 &position, const glm::vec2 &size, float rotation,
                                 const glm::vec4 &color)
{
    HZ_PROFILE_FUNC();
    if (s_Data.QuadIndexCount >= s_Data.MaxIndices)
        FlushAndReset();

    const float textureIndex = 0.0f, tilingFactor = 1.0f;
    glm::mat4 transform = glm::translate(glm::mat4(1.0f), position) *
                          glm::rotate(glm::mat4(1.0f), glm::radians(rotation), {0.0f, 0.0f, 0.1f}) *
                          glm::scale(glm::mat4(1.0f), {size.x, size.y, 1.0f});

    constexpr glm::vec2 TexCoods[4] = {{0.0f, 0.0f}, {1.0f, 0.0f}, {1.0f, 1.0f}, {0.0f, 1.0f}};

    for (int i = 0; i < 4; i++)
    {
        s_Data.QuadVertexBufferPtr->Position = transform * s_Data.QuadVertexPositions[i];
        s_Data.QuadVertexBufferPtr->Color = color;
        s_Data.QuadVertexBufferPtr->TexCoord = TexCoods[i];
        s_Data.QuadVertexBufferPtr->TexIndex = textureIndex;
        s_Data.QuadVertexBufferPtr->TilingFactor = tilingFactor;
        s_Data.QuadVertexBufferPtr++;
    }


    s_Data.QuadIndexCount += 6;

    s_Data.Stats.QuadCount++;
}

void Renderer2D::DrawRotatedQuad(const glm::vec2 &position, const glm::vec2 &size, float rotation,
                                 const Ref<Texture2D> &texture, float tilingfactor, const glm::vec4 &tilingcolor)
{
    DrawRotatedQuad({position.x, position.y, 0.0f}, size, rotation, texture, tilingfactor, tilingcolor);
}
void Renderer2D::DrawRotatedQuad(const glm::vec3 &position, const glm::vec2 &size, float rotation,
                                 const Ref<Texture2D> &texture, float tilingfactor, const glm::vec4 &tilingcolor)
{
    HZ_PROFILE_FUNC();
    if (s_Data.QuadIndexCount >= s_Data.MaxIndices)
        FlushAndReset();

    float textureIndex = 0.0f;
    for (uint32_t i = 1; i < s_Data.TextureSlotIndex; i++)
    {
        if (*texture.get() == *s_Data.TextureSlots.at(i).get())
        {
            textureIndex = (float)i;
            break;
        }
    }

    if (textureIndex == 0.0f)
    {
        textureIndex = (float)s_Data.TextureSlotIndex;
        s_Data.TextureSlots[(int)textureIndex] = texture;
        s_Data.TextureSlotIndex++;
    }

    glm::mat4 transform = glm::translate(glm::mat4(1.0f), position) *
                          glm::rotate(glm::mat4(1.0f), glm::radians(rotation), {0.0f, 0.0f, 0.1f}) *
                          glm::scale(glm::mat4(1.0f), {size.x, size.y, 1.0f});

    constexpr glm::vec2 TexCoods[4] = {{0.0f, 0.0f}, {1.0f, 0.0f}, {1.0f, 1.0f}, {0.0f, 1.0f}};

    for (int i = 0; i < 4; i++)
    {
        s_Data.QuadVertexBufferPtr->Position = transform * s_Data.QuadVertexPositions[i];
        s_Data.QuadVertexBufferPtr->Color = tilingcolor;
        s_Data.QuadVertexBufferPtr->TexCoord = TexCoods[i];
        s_Data.QuadVertexBufferPtr->TexIndex = textureIndex;
        s_Data.QuadVertexBufferPtr->TilingFactor = tilingfactor;
        s_Data.QuadVertexBufferPtr++;
    }

    s_Data.QuadIndexCount += 6;

    s_Data.Stats.QuadCount++;
}

void Renderer2D::DrawRotatedQuad(const glm::vec2 &position, const glm::vec2 &size, float rotation,
                                 const Ref<SubTexture2D> &subtexture, float tilingfactor, const glm::vec4 &tilingcolor)
{
    DrawRotatedQuad({position.x, position.y, 0.0f}, size, rotation, subtexture, tilingfactor, tilingcolor);
}
void Renderer2D::DrawRotatedQuad(const glm::vec3 &position, const glm::vec2 &size, float rotation,
                                 const Ref<SubTexture2D> &subtexture, float tilingfactor, const glm::vec4 &tilingcolor)
{
    HZ_PROFILE_FUNC();
    if (s_Data.QuadIndexCount >= s_Data.MaxIndices)
        FlushAndReset();

    float textureIndex = 0.0f;
    for (uint32_t i = 1; i < s_Data.TextureSlotIndex; i++)
    {
        if (*subtexture->GetTexture().get() == *s_Data.TextureSlots.at(i).get())
        {
            textureIndex = (float)i;
            break;
        }
    }

    if (textureIndex == 0.0f)
    {
        textureIndex = (float)s_Data.TextureSlotIndex;
        s_Data.TextureSlots[(int)textureIndex] = subtexture->GetTexture();
        s_Data.TextureSlotIndex++;
    }


    glm::mat4 transform = glm::translate(glm::mat4(1.0f), position) *
                          glm::rotate(glm::mat4(1.0f), glm::radians(rotation), {0.0f, 0.0f, 0.1f}) *
                          glm::scale(glm::mat4(1.0f), {size.x, size.y, 1.0f});

    for (int i = 0; i < 4; i++)
    {
        s_Data.QuadVertexBufferPtr->Position = transform * s_Data.QuadVertexPositions[i];
        s_Data.QuadVertexBufferPtr->Color = tilingcolor;
        s_Data.QuadVertexBufferPtr->TexCoord = subtexture->GetTexCoord()[i];
        s_Data.QuadVertexBufferPtr->TexIndex = textureIndex;
        s_Data.QuadVertexBufferPtr->TilingFactor = tilingfactor;
        s_Data.QuadVertexBufferPtr++;
    }

    s_Data.QuadIndexCount += 6;

    s_Data.Stats.QuadCount++;
}

Renderer2D::Statistcs Renderer2D::GetStats()
{
    return s_Data.Stats;
}

void Renderer2D::ResetStats()
{
    memset(&s_Data.Stats, 0, sizeof(Renderer2D::Statistcs));
}

void Renderer2D::FlushAndReset()
{
    EndScene();

    s_Data.QuadIndexCount = 0;
    s_Data.QuadVertexBufferPtr = s_Data.QuadVertexBufferBase;

    s_Data.TextureSlotIndex = 1;
}

} // namespace Hazel